<?php

namespace StudyBuddy;

use StudyBuddy\String\HTMLStringParser;

/**
 *
 * Class responsible for adding a new question
 * to QUESTIONS collection as well as updating
 * all Tags-related collections as well as increasing
 * count of user questions and updating per-user tags.
 *
 * This class does everything that has to be done
 * when new questions is submitted, regardless of how
 * it was submitted. It accepts an object of type
 * SubmittedQuestion which may be sub-classed to work with
 * many different ways question can be submitted: web, api, email, etc.
 *
 */
class QuestionParser extends StudyBuddyObject {

    /**
     * Object of type SubmittedQuestion
     * (or any sub-class of it)
     *
     * @var Object SubmittedQuestion
     */
    protected $oSubmitted;

    /**
     * New question object
     * created
     *
     * @var object of type Question
     */
    protected $oQuestion;
	protected $data;
    protected $oCache;

    public function __construct(Registry $oRegistry) {
        $this->oRegistry = $oRegistry;
        /**
         * Need to instantiate Cache so that it
         * will listen to event and unset some keys
         */
        $this->oCache = $this->oRegistry->Cache;
        $this->oRegistry->registerObservers('INPUT_FILTERS');
    }

    /**
     * Getter for submitted object
     * This can be used from observer object
     * like spam filter so that via oSubmitted
     * it's possible to call getUserObject()
     * and get user object of question submitter, then
     * look at some personal stats like reputation score,
     * usergroup, etc.
     *
     * @return object of type SubmittedQuestion
     */
    public function getSubmitted() {

        return $this->oSubmitted;
    }

    /**
     * Main entry method to start processing
     * the submitted question
     *
     * @param object $o object SubmittedQuestion
     */
    public function parse(SubmittedQuestion $o) {

        $this->oSubmitted = $o;

        $this->makeQuestion()
			->addToSearchIndex()
			->addTags()
			->addUnansweredTags()
			->addRelatedTags()
			->addUserTags()
			->makeActivities('q', 'asked a question', $this->data['_id']);
			//->makeActionCount('question_total');

        d('cp parsing done, returning question');

        return $this->oQuestion;
    }

    /**
     * Prepares data for the question object,
     * creates the $this->oQuestion object
     * and saves data to QUESTIONS collection
     *
     * @return object $this
     *
     * @throws QuestionParserException in case a filter (which is an observer)
     * either throws a FilterException (or sub-class of it) OR just cancells event
     *
     */
    protected function makeQuestion() {

        $oTitle = $this->oSubmitted->getTitle()->htmlentities()->trim();

        $username = $this->oSubmitted->getUserObject()->getDisplayName();

        $aTags = $this->oSubmitted->getTagsArray();

        /**
         * Must pass array('drop-proprietary-attributes' => false)
         * otherwise tidy removes rel="code"
         */
        $aEditorConfig = $this->oRegistry->Ini->getSection('EDITOR');
        $tidyConfig = ($aEditorConfig['ENABLE_CODE_EDITOR']) ? array('drop-proprietary-attributes' => false) : null;
        $oBody = $this->oSubmitted->getBody()->tidy($tidyConfig)->safeHtml()->asHtml();

        /**
         *
         * Now body is in html but we still need to run
         * it through HTMLStringParser string in order
         * to make clickable links and to
         * make sure all links are nofollow
         *
         */
        //$fp = fopen('/var/www/html/qa/www/php.log', 'a+');
        //fwrite($fp, print_r($aTags,1));
        //fclose($fp);

        $htmlBody = HTMLStringParser::factory($oBody)->parseCodeTags()->linkify()->importCDATA()->setNofollow()->hilightWords($aTags)->valueOf();
        d('after HTMLStringParser: ' . $htmlBody);

        $uid = $this->oSubmitted->getUserObject()->getUid();
        $hash = hash('md5', strtolower($htmlBody . json_encode($aTags)));

        /**
         * @todo can parse forMakrdown now but ideally
         * parseMarkdown() would be done inside Utf8string
         * as well as parseSmilies
         *
         * @todo later can also parse for smilies here
         *
         */
//        $this->checkForDuplicate($uid, $hash);

        $username = $this->oSubmitted->getUserObject()->getDisplayName();
        $time = time();
        /**
         *
         * @var array
         */
        $aData = array(
            '_id' => $this->oRegistry->Resource->create('QUESTION'),
            'level' => $this->oSubmitted->getLevel(),
            'subject' => $this->oSubmitted->getSubject(),
            'chapter' => $this->oSubmitted->getChapter(),
            'dif' => $this->oSubmitted->getDif(),
            'title' => $oTitle->valueOf(),
            /* 'title_hash' => hash('md5', strtolower(trim($title)) ), */
            'b' => $htmlBody,
            'hash' => $hash,
            'intro' => $this->oSubmitted->getBody()->asPlainText()->truncate(150)->valueOf(),
            'url' => $this->oSubmitted->getTitle()->toASCII()->makeLinkTitle()->valueOf(),
            'i_words' => $this->oSubmitted->getBody()->asPlainText()->getWordsCount(),
            'i_uid' => $uid,
            'username' => $username,
            'ulink' => '<a href="' . $this->oSubmitted->getUserObject()->getProfileUrl() . '">' . $username . '</a>',
            'avtr' => $this->oSubmitted->getUserObject()->getAvatarSrc(),
            'i_up' => 0,
            'i_down' => 0,
            'i_votes' => 0,
            'i_favs' => 0,
            'i_views' => 0,
            'a_tags' => $aTags,
            'a_title' => TitleTokenizer::factory($oTitle)->getArrayCopy(),
            'status' => 'unans',
            'tags_html' => \tplQtags::loop($aTags, false),
            'credits' => '',
            'i_ts' => $time,
            'hts' => date('F j, Y g:i a T'),
            'i_lm_ts' => $time,
            'i_ans' => 0,
            'ans_s' => 's',
            'v_s' => 's',
            'f_s' => 's',
            'ip' => $this->oSubmitted->getIP(),
            'app' => $this->oSubmitted->getApp(),
            'app_id' => $this->oSubmitted->getAppId(),
            'app_link' => $this->oSubmitted->getAppLink(),
            'i_flwrs' => 1 // initially question has 1 follower - its author
        );
		
        /**
         * Submitted question object may provide
         * extra elements to be added to aData array
         * This is usually useful for parsing questions that
         * came from external API, in which case the answered/unanswred
         * status as well as number of answers is already known
         *
         * as well as adding 'credit' div
         */
        $aExtraData = $this->oSubmitted->getExtraData();
        d('$aExtraData: ' . print_r($aExtraData, 1));
        if (is_array($aExtraData) && !empty($aExtraData)) {
            $aData = array_merge($aData, $aExtraData);
        }

        $this->oQuestion = new Question($this->oRegistry, $aData);

        /**
         * Post onBeforeNewQuestion event
         * and watch for filter either cancelling the event
         * or throwing FilterException (prefferred way because
         * a specific error message can be passed in FilterException
         * this way)
         *
         * In either case we throw QuestionParserException
         * Controller that handles the question form should be ready
         * to handle this exception and set the form error using
         * message from exception. This way the error will be shown to
         * the user right on the question form while question form's data
         * is preserved in form.
         *
         * Filter can also modify the data in oQuestion before
         * it is saved. This is convenient, we can even set different
         * username, i_uid if we want to 'post as alias'
         */
        try {
            $oNotification = $this->oRegistry->Dispatcher->post($this->oQuestion, 'onBeforeNewQuestion');
            if ($oNotification->isNotificationCancelled()) {
                throw new QuestionParserException('Sorry, we are unable to process your question at this time.');
            }
        } catch (FilterException $e) {
            e('Got filter exteption: ' . $e->getFile() . ' ' . $e->getLine() . ' ' . $e->getMessage() . ' ' . $e->getTraceAsString());
            throw new QuestionParserException($e->getMessage());
        }

        /**
         * Do ensureIndexes() now and not before we are sure that we even going
         * to add a new question.
         */
        $this->ensureIndexes();

		$this->data = $aData;
		
        $this->oQuestion->insert();
		
		$this->makeActionCount('question_total'); //hoangto action count
				
        $this->followQuestion();

        $this->oRegistry->Dispatcher->post($this->oQuestion, 'onNewQuestion');

        return $this;
    }

    /**
     * Adds Question to array of user's followed
     * questions
     * and adds user details to array of Question's followers
     *
     * @return object $this
     */
    protected function followQuestion() {

        /**
         * For consistant behaviour it is
         * Best is to go through FollowManager and don't
         * do this manually
         */
        FollowManager::factory($this->oRegistry)->followQuestion($this->oRegistry->Viewer, $this->oQuestion);

        return $this;
    }

    /**
     * Ensure indexes in all collections involved
     * in storing question data
     *
     * @return object $this
     */
    protected function ensureIndexes() {
        $quest = $this->oRegistry->Mongo->QUESTIONS;
        $quest->ensureIndex(array('i_sticky' => 1));
        $quest->ensureIndex(array('i_ts' => 1));
        $quest->ensureIndex(array('i_votes' => 1));
        $quest->ensureIndex(array('i_ans' => 1));
        $quest->ensureIndex(array('a_tags' => 1));
        $quest->ensureIndex(array('i_uid' => 1));
        $quest->ensureIndex(array('hash' => 1));
        $quest->ensureIndex(array('a_title' => 1));

        /**
         * Need ip index to use flood filter by ip
         * and to quickly find all posts by ip
         * in case of deleting a spam.
         *
         */
        $quest->ensureIndex(array('ip' => 1));

        /**
         * Index a_f_q in USERS (array of followed question ids)
         * @todo move this to when the user is created!
         */
        $this->oRegistry->Mongo->USERS->ensureIndex(array('a_f_q' => 1));

        return $this;
    }

	//hoangto make question activity
	protected function makeActivities($act_type = 'q', $act_name = 'asked a question', $act_id) {
		$actData = array(
			'i_uid' => $this->data['i_uid'],
			'username' => $this->data['username'],
			'avt' => $this->data['avtr'],
			'act_type' => $act_type,
			'act_id' => $act_id,
			'act_name' => $act_name,
			'i_ts' => time(),
			'hts' => date('F j, Y g:i a T')
		);
	
		$coll = $this->oRegistry->Mongo->ACTIVITIES;
		$coll->ensureIndex(array('i_uid' => 1));
		$coll->ensureIndex(array('i_ts' => 1));
		
		$oActivity = \StudyBuddy\MongoDoc::factory($this->oRegistry, 'ACTIVITIES', $actData);
		$res = $oActivity->save($actData);
		
		return $this;
	}
	
	//hoangto make action count
	protected function makeActionCount($countType = ''){
	
		$coll = $this->oRegistry->Mongo->ACTIONCOUNT;
	
		$count = $coll->findOne(array('i_uid' => $this->data['i_uid']), array('_id',$countType));
		
		if(empty($count[$countType])){
			$total = 1;
		}else{
			$total = (int)$count[$countType] + 1;
		}
			
		if(empty($count['_id'])){
			$countData = array(
				'i_uid' => $this->data['i_uid'],
				$countType => $total
			);
			$this->oRegistry->Mongo->ACTIONCOUNT->insert($countData);
		}else{
			$countData = array(
				'$set' => array($countType => $total)
			);
			$this->oRegistry->Mongo->ACTIONCOUNT->update(array('i_uid' => $this->data['i_uid']),$countData);
		}
		
		$coll->ensureIndex(array('i_uid' => 1));
		
		$this->checkGainBadges($countType, $this->data['i_uid']);
				
		return $this;
	}
	
	protected function checkGainBadges($countType, $uid){
		
		$count = $this->oRegistry->Mongo->ACTIONCOUNT->findOne(array('i_uid' => $uid), array($countType));
		$cond = $this->oRegistry->Mongo->BADGES->find(array('cond_type' => $countType, 'cond' => array('$lte' => $count[$countType])));
		
		foreach($cond as $obj){
			$actData = array(
				'i_uid' => $uid,
				'username' => $this->data['username'],
				'i_bid' => $obj['id'],
				'badges' => $obj['b_name'],
				'badges_type' => $obj['type']
			);
			$isExist = $this->oRegistry->Mongo->USER_BADGES->findOne($actData);
			if(empty($isExist)){
				$this->oRegistry->Mongo->USER_BADGES->insert($actData);
				$this->makeActivities('badges', 'earned a badges', $obj['id']);
			}
			$this->oRegistry->Mongo->USER_BADGES->ensureIndex(array('i_uid' => 1));
			$this->oRegistry->Mongo->USER_BADGES->ensureIndex(array('badges' => 1));
		}
		
		return $this;
	}
	
    /**
     * Check to see if same user has already posted
     * exact same question
     *
     * @todo translate the error message
     *
     * @param int $uid
     * @param string $hash hash of question body
     */
    protected function checkForDuplicate($uid, $hash) {
        $a = $this->oRegistry->Mongo->QUESTIONS->findOne(array('i_uid' => $uid, 'hash' => $hash));
        if (!empty($a)) {
            $err = 'You have already asked exact same question  <span title="' . $a['hts'] . '" class="ts" rel="time">on ' . $a['hts'] .
                    '</span><br><a class="link" href="/questions/' . $a['_id'] . '/' . $a['url'] . '">' . $a['title'] . '</a><br>
			You cannot post the same exact question twice';

            throw new QuestionParserException($err);
        }
    }

    /**
     * Index question
     *
     * @todo do this via runLater
     *
     * @return object $this
     */
    protected function addToSearchIndex() {

        IndexerFactory::factory($this->oRegistry)->indexQuestion($this->oQuestion);

        return $this;
    }

    /**
     * Update QUESTION_TAGS tags counter
     *
     * @return object $this
     */
    protected function addTags() {

        $o = Qtagscounter::factory($this->oRegistry);
        $oQuestion = $this->oQuestion;
        if (count($oQuestion['a_tags']) > 0) {
            $callable = function() use($o, $oQuestion) {
                        $o->parse($oQuestion);
                    };
            d('cp');
            runLater($callable);
        }

        return $this;
    }

    /**
     * Calculates related tags
     * via shutdown function
     *
     * @return object $this
     */
    protected function addRelatedTags() {

        $oRelated = Relatedtags::factory($this->oRegistry);
        $oQuestion = $this->oQuestion;

        if (count($oQuestion['a_tags']) > 0) {
            d('cp');
            $callable = function() use ($oRelated, $oQuestion) {
                        $oRelated->addTags($oQuestion);
                    };
                    
            runLater($callable);
        }
        d('cp');

        return $this;
    }

    /**
     * Skip if $this->oQuestion['status'] is accptd
     * which would be the case when question came from API
     * and is already answered
     *
     * @return object $this
     */
    protected function addUnansweredTags() {
        if ('accptd' !== $this->oQuestion['status']) {
            if (count($this->oQuestion['a_tags']) > 0) {
                $o = new UnansweredTags($this->oRegistry);
                $oQuestion = $this->oQuestion;
                $callable = function() use ($o, $oQuestion) {
                            $o->set($oQuestion);
                        };
                d('cp');
                runLater($callable);
            }
            d('cp');
        }

        return $this;
    }

    /**
     * Update USER_TAGS collection
     *
     * @return object $this
     */
    protected function addUserTags() {

        $oUserTags = UserTags::factory($this->oRegistry);
        $uid = $this->oSubmitted->getUserObject()->getUid();
        $oQuestion = $this->oQuestion;
        if (count($oQuestion['a_tags']) > 0) {
            $callable = function() use ($oUserTags, $uid, $oQuestion) {
                        $oUserTags->addTags($uid, $oQuestion);
                    };

            runLater($callable);
        }

        return $this;
    }

}
